﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace Romeo.Data
{
    /// <summary>
    ///     The representation of an Othello board.
    /// </summary>
    /// <remarks><para>
    ///     For optimization reasons, the board represents the players as the
    ///     "Computer" and the "Opponent", instead of "Black" and "White". This
    ///     makes move generators and position evaluators more efficient since
    ///     they can use constants instead of variables at many places.
    /// </para></remarks>
    public class Board
    {
        public const int CellCount = 100;
        public const int StartCell = 11;
        public const int EndCell = 89;
        public const int UsableCount = EndCell - StartCell;

        public const sbyte Empty = 0;
        public const sbyte Computer = 1;
        public const sbyte Opponent = -1;
        public const sbyte Invalid = 3;

        private Move _RecycledMove1;
        private Move _RecycledMove2;
        
#if DEBUG
        private int _tid;
#endif

        internal sbyte []_Cells = new sbyte [CellCount];
        internal int _Remaining;
        internal string _Name;
        private bool _JustSwitched;

        public Board (string aName)
        {
            for (int i = 0; i < 9; i++)
            {
                _Cells[i] = Invalid;
                _Cells[i + 91] = Invalid;
                _Cells[10 * i + 10] = Invalid;
                _Cells[10 * i + 9] = Invalid;
            }
            _Cells[45] = Computer;
            _Cells[54] = Computer;
            _Cells[44] = Opponent;
            _Cells[55] = Opponent;
            _Remaining = 60;
            _Name = aName;
        }

        public Board (Board aSource, string aSubName)
        {
            for (int i = 0; i < CellCount; i++)
            {
                _Cells[i] = aSource._Cells[i];
            }
            _Remaining = aSource._Remaining;
            _Name = "Copy of (" + aSource._Name + ") for " + aSubName;
        }

        public override int GetHashCode ()
        {
            int h = _Remaining;
            for (int i = 11; i < 89; i++)
            {
                h = ((h >> 24) + (h << 2) + (i + 1) * (_Cells[i] + 1)) & 0xFFFFFF;
            }
            return base.GetHashCode ();
        }

        public override bool Equals (object obj)
        {
            Board b = obj as Board;
            if (b == null || b._Remaining != _Remaining)
            {
                return false;
            }

            for (int i = 11; i < 89; i++)
            {
                if (b._Cells[i] != _Cells[i])
                {
                    return false;
                }
            }
            return true;
        }

        public int CountFlips (int aCell, sbyte aPlayer, int aDirection)
        {
            int n = 0;
            while (_Cells[aCell += aDirection] == -aPlayer)
            {
                n++;
            }

            if (_Cells[aCell] == aPlayer) 
                return n; 
            else 
                return 0;
        }

        public int CountFlips (int aCell, sbyte aPlayer)
        {
            return CountFlips (aCell, aPlayer, 1)
                + CountFlips (aCell, aPlayer, -1)
                + CountFlips (aCell, aPlayer, 10)
                + CountFlips (aCell, aPlayer, -10)
                + CountFlips (aCell, aPlayer, 9)
                + CountFlips (aCell, aPlayer, -9)
                + CountFlips (aCell, aPlayer, 11)
                + CountFlips (aCell, aPlayer, -11);
        }

        public bool CanPlay (int aCell, sbyte aPlayer)
        {
            if (aCell == 0)
                return true;
            if (_Cells[aCell] != Empty)
                return false;
            return CountFlips (aCell, aPlayer, 1) > 0
                || CountFlips (aCell, aPlayer, -1) > 0
                || CountFlips (aCell, aPlayer, 10) > 0
                || CountFlips (aCell, aPlayer, -10) > 0
                || CountFlips (aCell, aPlayer, 9) > 0
                || CountFlips (aCell, aPlayer, -9) > 0
                || CountFlips (aCell, aPlayer, 11) > 0
                || CountFlips (aCell, aPlayer, -11) > 0;
        }

        public bool CanPass (sbyte aPlayer)
        {
            if (_Remaining == 60 || _JustSwitched)
            {
                return true;
            }
            for (int i = StartCell; i < EndCell; i++)
            {
                if (CanPlay (i, aPlayer))
                {
                    return false;
                }
            }
            return true;
        }

        public sbyte this[int aCell]
        {
            get
            {
                return _Cells[aCell];
            }
            set
            {
                _Cells[aCell] = value;
            }
        }

        private void Expect (int aHash)
        {
            int myHash = GetHashCode ();

            if (myHash != aHash)
            {
                myHash = myHash + 1;
                aHash = aHash + 1;
                Debug.Assert (myHash == aHash);
            }
        }

        private void MakeFlip (int aCell, sbyte aPlayer, Move aMove, int aDirection)
        {
            int n = 0;
            while (_Cells[aCell += aDirection] == -aPlayer)
            {
                n++;
            }

            if (_Cells[aCell] == aPlayer && n > 0)
            {
                while (_Cells[aCell -= aDirection] == -aPlayer)
                {
                    aMove._Flips[aMove._FlipCount++] = (byte)aCell;
                    _Cells[aCell] = aPlayer;
                }
            }

        }

        public Move GetMove ()
        {
#if DEBUG
            int thisThread = Thread.CurrentThread.ManagedThreadId;
            if (_tid == 0)
            {
                _tid = thisThread;
            }
            else
            {
                Debug.Assert (_tid == thisThread, String.Format ("Access {2} from {0} while expecting {1}", thisThread, _tid, _Name));
            }
#endif

            Move m;
            if (_RecycledMove1 != null)
            {
                m = _RecycledMove1;
                _RecycledMove1 = null;
#if DEBUG
                Debug.Assert (m._CacheStatus == 1);
                m._CacheStatus = 0;
#endif
            }
            else if (_RecycledMove2 != null)
            {
                m = _RecycledMove2;
                _RecycledMove2 = null;
#if DEBUG
                Debug.Assert (m._CacheStatus == 2);
                m._CacheStatus = 0;
#endif
            }
            else
            {
                m = new Move ();
            }
            return m;
        }

        public void RecycleMove (Move aMove)
        {
#if DEBUG
            Debug.Assert (aMove._CacheStatus == 0);
#endif

            if (_RecycledMove1 == null)
            {
                _RecycledMove1 = aMove;
#if DEBUG
                aMove._CacheStatus = 1;
#endif
            }
            else if (_RecycledMove2 == null)
            {
                _RecycledMove2 = aMove;
#if DEBUG
                aMove._CacheStatus=2;
#endif
            }
        }

        public Move PlayOn (int aCell, sbyte aPlayer)
        {
            _JustSwitched = false;
            if (aCell == 0)
            {
                Move pass = GetMove ();
                pass.Reset (aCell, aPlayer);
#if DEBUG
                pass._PreImage = pass._PostImage = GetHashCode ();
#endif
                return pass;
            }

            if (_Cells[aCell] != Empty)
            {
                return null;
            }

            Move m = GetMove ();

            m.Reset (aCell, aPlayer);
#if DEBUG
            m._PreImage = GetHashCode ();
#endif
            MakeFlip (aCell, aPlayer, m, 1);
            MakeFlip (aCell, aPlayer, m, 9);
            MakeFlip (aCell, aPlayer, m, 10);
            MakeFlip (aCell, aPlayer, m, 11);
            MakeFlip (aCell, aPlayer, m, -1);
            MakeFlip (aCell, aPlayer, m, -9);
            MakeFlip (aCell, aPlayer, m, -10);
            MakeFlip (aCell, aPlayer, m, -11);
            if (m._FlipCount > 0)
            {
                _Cells[aCell] = aPlayer;
                _Remaining--;
#if DEBUG
                m._PostImage = GetHashCode();
#endif
                return m;
            }
            else
            {
                RecycleMove (m);
                return null;
            }
        }

        public void Undo (Move aMove)
        {
#if DEBUG
            Expect (aMove._PostImage);
#endif

            if (aMove._Cell == 0)
            {
                return;
            }

            sbyte p = (sbyte)-aMove._Player;
            _Cells[aMove._Cell] = Empty;
            for (int i = 0; i < aMove._FlipCount; i++)
            {
                _Cells[aMove._Flips[i]] = p;
            }
            _Remaining++;

#if DEBUG
            Expect (aMove._PreImage);
#endif

        }

        public void Redo (Move aMove)
        {
#if DEBUG
            Expect (aMove._PreImage);
#endif

            if (aMove._Cell == 0)
            {
                return;
            }

            sbyte p = aMove._Player;
            _Cells[aMove._Cell] = p;
            for (int i = 0; i < aMove._FlipCount; i++)
            {
                _Cells[aMove._Flips[i]] = p;
            }
            _Remaining--;
#if DEBUG
            Expect (aMove._PostImage);
#endif
        }

        internal void SwitchSides ()
        {
            _JustSwitched = true;
            for (int i = StartCell; i < EndCell; i++)
            {
                if (_Cells[i] == Computer)
                {
                    _Cells[i] = Opponent;
                }
                else if (_Cells[i] == Opponent)
                {
                    _Cells[i] = Computer;
                }
            }
        }
    }
}
